package com.shujia.day14;

public class Demo01Synchronized {
    public static void main(String[] args) {
        /*
            TODO :
                问题:
                    1.对于总的销售票数 会大于100张
                    2.对于票数可能会出现负数 不符合代码的执行逻辑
                总结:对于多个线程使用同一个变量,那么会造成线程不安全的问题
                        如何解决? => 线程同步 => 当使用一个变量时,会对当前变量进行加锁,不让其他线程获取到变量
                                                     但是其他线程可以获取到CPU的执行权
                解决思路：
                    对于共享变量的使用，需要对其进行加锁操作
                    格式：
                        synchronized(对象){需要同步的代码;}
                    注意：
                        ① 如果当 synchronized 将 sell 方法进行对当前对象加锁，那么可能会出现售票为0的现象
                            原因：while循环中的ticksNum可能会被其他的线程所抢占CPU执行导致当售票为0时能进入循环
                        ② 如果当 synchronized 将while循环进行对当前对象枷锁，那么对导致只有一个线程运行
                            原因： 当一个线程获取到 sellRunnable对象之后对其进行加锁
                                        加锁以后没有释放，所以会导致一个线程会一直拥有对象，其他线程没有办法获取到
                                            对象，就没有办法执行while循环
                            如何释放锁？
                                对于synchronized修饰的代码块，当执行完成后，那么会对其进行释放锁

         */
        // sellRunnable对象是全局唯一的，并且其中的变量ticksNum也是唯一的
//        SellRunnable sellRunnable = new SellRunnable();
//        new Thread(sellRunnable, "售票员1").start();
//        new Thread(sellRunnable, "售票员2").start();

        // 当每个线程拥有自己独有的对象，那么只对当前自己对象加锁不影响其他线程
//        SellRunnable sellRunnable1 = new SellRunnable();
//        SellRunnable sellRunnable2 = new SellRunnable();
//        new Thread(sellRunnable1, "售票员1").start();
//        new Thread(sellRunnable2, "售票员2").start();

        // 可以同步
//        TicksNum ticksNum = new TicksNum();
//        new SynchronizedThread("售票员1",ticksNum).start();
//        new SynchronizedThread("售票员2",ticksNum).start();

        // 不可以同步：
        TicksNum ticksNum1 = new TicksNum();
        TicksNum ticksNum2 = new TicksNum();
        new SynchronizedThread("售票员1",ticksNum1).start();
        new SynchronizedThread("售票员2",ticksNum2).start();

    }

    static class SellRunnable implements Runnable {
        int ticksNum = 100;

        @Override
        public void run() {
            // TODO 一旦当线程获取到唯一的一个this对象，其他线程如果想要执行同步代码块，必须要等当前线程释放

            // 同步代码块
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                synchronized (this) {
                    // TODO this 代表当前 SellRunnable 的对象 => 在main方法中只有一个sellRunnable对象
                    if (ticksNum > 0) {
                        sell();
                    } else {
                        break;
                    }
                }
            }
        }

        // 销售的方法
        public void sell() {
            System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + ticksNum + "票");
            ticksNum -= 1;
        }
    }

    static class SynchronizedThread extends Thread {
        TicksNum ticksNum;  // TODO 对于ticksNum可以是唯一的一个对象

        public SynchronizedThread(String name, TicksNum ticksNum) {
            super(name);
            this.ticksNum = ticksNum;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // TODO 如果要加锁，那么需要寻找唯一存在的东西  可以使用传入的 ticksNum  => 但是要求只能传入一个
                synchronized (ticksNum) {
                    // TODO this 代表的是 SynchronizedThread 的对象
                    if (ticksNum.getNum() > 0) {
                        sell();
                    } else {
                        break;
                    }
                }
            }
        }

        public void sell() {
            System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + ticksNum.getNum() + "票");
            ticksNum.sellTick();
        }
    }

    static class TicksNum {
        int num = 100;

        public void sellTick() {
            num -= 1;
        }

        public int getNum() {
            return num;
        }
    }

}

